package main

import (
	"errors"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"strings"

	"github.com/urfave/cli"
)

type filefilter struct {
	mode string
	item []string
}

func getFileInfo(src string) os.FileInfo {
	if fileInfo, e := os.Stat(src); e != nil {
		if os.IsNotExist(e) {
			return nil
		}
		return nil
	} else {
		return fileInfo
	}
}

func copyFile(src, dst string) bool {
	if len(src) == 0 || len(dst) == 0 {
		return false
	}
	srcFile, e := os.OpenFile(src, os.O_RDONLY, os.ModePerm)
	if e != nil {
		return false
	}
	defer func() {
		srcFile.Close()
	}()

	dst = strings.Replace(dst, "\\", "/", -1)
	dstPathArr := strings.Split(dst, "/")
	dstPathArr = dstPathArr[0 : len(dstPathArr)-1]
	dstPath := strings.Join(dstPathArr, "/")

	dstFileInfo := getFileInfo(dstPath)
	if dstFileInfo == nil {
		if e := os.MkdirAll(dstPath, os.ModePerm); e != nil {
			return false
		}
	}
	dstFile, e := os.OpenFile(dst, os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0644)
	if e != nil {
		return false
	}
	defer dstFile.Close()
	if _, e := io.Copy(dstFile, srcFile); e != nil {
		return false
	} else {
		return true
	}
}

func copyPath(src, dst string, filter *filefilter) bool {
	srcFileInfo := getFileInfo(src)
	if srcFileInfo == nil || !srcFileInfo.IsDir() {
		fmt.Println("parameter error")
		return false
	}
	err := filepath.Walk(src, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return nil
		}
		relationPath := strings.Replace(path, src, "/", -1)
		dstPath := strings.TrimRight(strings.TrimRight(dst, "/"), "\\") + relationPath
		if !info.IsDir() {
			basename := info.Name()
			ss := strings.Split(basename, ".")
			if len(ss) != 2 {
				return nil
			}
			if filter.mode == "" {
			} else if filter.mode == "wl" {
				flag := false
				for _, fl := range filter.item {
					if fl == ss[1] {
						flag = true
						break
					}
				}
				if !flag {
					return nil
				}
			} else if filter.mode == "bl" {
				for _, fl := range filter.item {
					if fl == ss[1] {
						return nil
					}
				}
			}

			switch ss[1] {
			case "cpp", "cc", "h", "hpp":
			default:
				return nil
			}

			if copyFile(path, dstPath) {
				return nil
			} else {
				return errors.New(path + " copy fail")
			}
		} else {
			if _, err := os.Stat(dstPath); err != nil {
				if os.IsNotExist(err) {
					if err := os.MkdirAll(dstPath, os.ModePerm); err != nil {
						return err
					} else {
						return nil
					}
				} else {
					return err
				}
			} else {
				return nil
			}
		}
	})

	if err != nil {
		return false
	}
	return true

}

func main() {
	app := &cli.App{
		Name:  "tangcp",
		Usage: "./tangcp --src dir_src --dst dir_dst [--whitelist|--blacklist] [cpp,h,sh,go,java]",
		Flags: []cli.Flag{
			&cli.StringFlag{
				Name:  "src",
				Usage: "the source root path of the file to be copied",
			},
			&cli.StringFlag{
				Name:  "dst",
				Usage: "file copy destination path",
			},
			&cli.StringFlag{
				Name:  "whitelist",
				Usage: "whitelist filter key by file's type",
			},
			&cli.StringFlag{
				Name:  "blacklist",
				Usage: "blacklist filter key by file's type",
			},
		},
		Action: func(cctx *cli.Context) error {
			src := ""
			dst := ""
			wl := ""
			bl := ""
			if src = cctx.String("src"); src == "" {
				return errors.New("--src src-dir")
			}
			if dst = cctx.String("dst"); dst == "" {
				return errors.New("--src dst-dir")
			}
			wl = cctx.String("whitelist")
			bl = cctx.String("blacklist")
			if wl != "" && bl != "" {
				return errors.New("cannot use whitelist and blacklist mode at the same time")
			}
			fmt.Println("start copy", src, dst)
			copyPath(src, dst, &filefilter{mode: func() string {
				if wl == "" && bl == "" {
					return ""
				} else if wl == "" && bl != "" {
					return "bl"
				} else {
					return "wl"
				}
			}(),
				item: func() []string {
					if wl == "" && bl == "" {
						return nil
					}
					filter_key := wl + bl
					if len(filter_key) == 0 {
						return nil
					} else {
						sList := strings.Split(filter_key, ",")
						ss := make([]string, len(sList))
						for i := range sList {
							ss[i] = sList[i]
						}

						return ss
					}
				}(),
			})

			return nil
		},
	}
	app.Setup()
	app.Run(os.Args)
}
